/*
 *  Copyright 2020 Huawei Technologies Co., Ltd.
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package org.edgegallery.mecm.inventory.service;

import com.google.common.collect.Lists;
import java.util.LinkedList;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Optional;
import org.edgegallery.mecm.inventory.apihandler.dto.MecHostDto;
import org.edgegallery.mecm.inventory.exception.InventoryException;
import org.edgegallery.mecm.inventory.model.BaseModel;
import org.edgegallery.mecm.inventory.model.MecHost;
import org.edgegallery.mecm.inventory.service.repository.BaseRepository;
import org.edgegallery.mecm.inventory.service.repository.MecHostRepository;
import org.edgegallery.mecm.inventory.utils.Constants;
import org.edgegallery.mecm.inventory.utils.InventoryUtilities;
import org.edgegallery.mecm.inventory.utils.Status;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Service;

/**
 * Implementation of inventory service.
 */
@Service("InventoryServiceImpl")
public final class InventoryServiceImpl implements InventoryService {

    private static final Logger LOGGER = LoggerFactory.getLogger(InventoryServiceImpl.class);
    private static final String TENANT_NOT_FOUND_MESSAGE = "Record corresponding to tenant identifier {} not exists";
    private static final String ID_NOT_FOUND_MESSAGE = "Record corresponding to identifier {} not exists";
    private static final String ROLE_ADMIN = "ROLE_MECM_ADMIN";

    @Autowired
    private TenantServiceImpl tenantService;
    @Autowired
    private MecHostRepository repository;

    @Override
    public <T extends BaseModel> Status addRecord(T model, CrudRepository<T, String> repository) {
        if (repository.existsById(model.getIdentifier())) {
            LOGGER.error("{} record corresponding to identifier {} already exists", model.getType(),
                    model.getIdentifier());
            throw new IllegalArgumentException("Record already exist");
        }
        long size;
        List<T> recordList = null;
        String tenantId = model.getTenantId();
        if (tenantId != null) {
            recordList = ((BaseRepository) repository).findByTenantId(tenantId);

            if (tenantService.isMaxTenantCountReached() && !tenantService.isTenantExist(tenantId)) {
                LOGGER.error("Max tenant count {} reached", Constants.MAX_TENANTS);
                throw new InventoryException(Constants.MAX_LIMIT_REACHED_ERROR);
            }
            size = recordList.size();
        } else {
            size = repository.count();
        }

        if (size == Constants.MAX_ENTRY_PER_TENANT_PER_MODEL) {
            LOGGER.error("Max entry per model {} reached", Constants.MAX_ENTRY_PER_TENANT_PER_MODEL);
            throw new InventoryException(Constants.MAX_LIMIT_REACHED_ERROR);
        }

        repository.save(model);
        LOGGER.info("Record added for identifier {} & type {}", model.getIdentifier(), model.getType());
        if (tenantId != null) {
            tenantService.addTenant(tenantId, model.getType());
        }
        return new Status("Saved");
    }

    @Override
    public <T extends BaseModel> Status updateRecord(T model, CrudRepository<T, String> repository) {
        if (!repository.existsById(model.getIdentifier())) {
            LOGGER.error("{} record corresponding to identifier {} not exists", model.getType(),
                    model.getIdentifier());
            throw new NoSuchElementException(Constants.RECORD_NOT_FOUND_ERROR);
        }
        repository.save(model);
        LOGGER.info("Record updated for identifier {} & type {}", model.getIdentifier(), model.getType());
        return new Status("Updated");
    }

    @Override
    public <T extends BaseModel> List<T> getTenantRecords(String tenantId, CrudRepository<T, String> repository) {
        List<T> recordList;
        if (tenantId != null) {
            recordList = ((BaseRepository) repository).findByTenantId(tenantId);
            if (recordList == null || recordList.isEmpty()) {
                LOGGER.error(TENANT_NOT_FOUND_MESSAGE, tenantId);
                throw new NoSuchElementException(Constants.RECORD_NOT_FOUND_ERROR);
            }
            LOGGER.info("Records returned for tenant identifier {} & type {}", recordList.get(0).getTenantId(),
                recordList.get(0).getType());
        } else {
            Iterable<T> records = repository.findAll();
            if (!records.iterator().hasNext()) {
                LOGGER.error(Constants.RECORD_NOT_FOUND_ERROR);
                throw new NoSuchElementException(Constants.RECORD_NOT_FOUND_ERROR);
            }
            recordList = Lists.newArrayList(repository.findAll());
            LOGGER.info("Records returned for the type {}", recordList.get(0).getType());
        }
        return recordList;
    }

    @Override
    public <T extends BaseModel> List<T> getRecordsByRole(String role, CrudRepository<T, String> repository) {
        List<T> recordList;
        Iterable<T> records = ((BaseRepository) repository).findByUserRole(role);
        if (!records.iterator().hasNext()) {
            LOGGER.error(Constants.RECORD_NOT_FOUND_ERROR);
            throw new NoSuchElementException(Constants.RECORD_NOT_FOUND_ERROR);
        }
        recordList = Lists.newArrayList(repository.findAll());
        LOGGER.info("Records returned for the type {}", recordList.get(0).getType());
        return recordList;
    }

    @Override
    public <T extends BaseModel> T getRecord(String id, CrudRepository<T, String> repository) {
        Optional<T> recordOption = repository.findById(id);
        if (!recordOption.isPresent()) {
            LOGGER.error(ID_NOT_FOUND_MESSAGE, id);
            throw new NoSuchElementException(Constants.RECORD_NOT_FOUND_ERROR);
        }
        LOGGER.info("Record returned for identifier {} & type {}", recordOption.get().getIdentifier(),
            recordOption.get().getType());
        return recordOption.get();
    }

    @Override
    public <T extends BaseModel> Status deleteTenantRecords(String tenantId, CrudRepository<T, String> repository) {
        List<T> recordList;
        if (tenantId != null) {
            recordList = ((BaseRepository) repository).findByTenantId(tenantId);
            if (recordList == null || recordList.isEmpty()) {
                LOGGER.error(TENANT_NOT_FOUND_MESSAGE, tenantId);
                throw new NoSuchElementException(Constants.RECORD_NOT_FOUND_ERROR);
            }
            ((BaseRepository) repository).deleteByTenantId(tenantId);
            LOGGER.info("Record deleted for tenant identifier {} & type {}", recordList.get(0).getTenantId(),
                recordList.get(0).getType());
            tenantService.clearCount(tenantId, recordList.get(0).getType());
        } else {
            repository.deleteAll();
        }
        return new Status("Deleted");
    }

    @Override
    public <T extends BaseModel> Status deleteRecord(String id, CrudRepository<T, String> repository) {
        Optional<T> recordOption = repository.findById(id);
        if (!recordOption.isPresent()) {
            LOGGER.error(ID_NOT_FOUND_MESSAGE, id);
            throw new NoSuchElementException(Constants.RECORD_NOT_FOUND_ERROR);
        }
        BaseModel model = recordOption.get();
        String tenantId = model.getTenantId();
        repository.deleteById(id);
        if (tenantId != null) {
            LOGGER.info("Record deleted for identifier {} & type {}", tenantId, model.getType());
            tenantService.reduceCount(tenantId, model.getType());
        } else {
            LOGGER.info("Record deleted for the type {}", model.getType());
        }

        return new Status("Deleted");
    }

    private boolean addAdminRoleRecord(List<MecHost> mecHostsAdmin, List<MecHostDto> mecHostDtos) {
        List<MecHost> mecHosts = getTenantRecords(null, repository);
        for (MecHost mecHost : mecHosts) {
            if (mecHost.getRole().equals(ROLE_ADMIN)) {
                mecHostsAdmin.add(mecHost);
            }
        }
        if (InventoryUtilities.hasRole(ROLE_ADMIN)) {
            for (MecHost host : mecHostsAdmin) {
                MecHostDto mecHostDto = InventoryUtilities.getModelMapper().map(host, MecHostDto.class);
                mecHostDtos.add(mecHostDto);
            }
            return true;
        }
        return false;
    }

    private boolean checkEmpty(List<MecHost> mecHostsAdmin, List<MecHostDto> mecHostDtos) {
        if (mecHostDtos.isEmpty()) {
            for (MecHost host : mecHostsAdmin) {
                MecHostDto mecHostDto = InventoryUtilities.getModelMapper().map(host, MecHostDto.class);
                mecHostDtos.add(mecHostDto);
            }
            return true;
        }
        return false;
    }

    private List<MecHostDto> addHostDtos(List<MecHost> mecHostsAdmin, List<MecHostDto> mecHostDtos) {
        List<MecHostDto> retHostDtos = new LinkedList<>();
        for (MecHostDto hostDto : mecHostDtos) {
            retHostDtos.add(hostDto);
        }

        for (MecHost mecHostAdmin : mecHostsAdmin) {
            for (MecHostDto tenantMecHost : mecHostDtos) {
                if (!mecHostAdmin.getMechostIp().equals(tenantMecHost.getMechostIp())) {
                    MecHostDto mecHostDto = InventoryUtilities.getModelMapper().map(mecHostAdmin, MecHostDto.class);
                    retHostDtos.add(mecHostDto);
                }
            }
        }
        return retHostDtos;
    }

    @Override
    public List<MecHostDto> getAllMecHostRecords(String tenantId, List<MecHost> mecHostsAdmin,
        List<MecHostDto> mecHostDtos) {
        try {
            // if role is admin, just return all records belongs to admin role users.
            if (addAdminRoleRecord(mecHostsAdmin, mecHostDtos)) {
                return mecHostDtos;
            }
        } catch (NoSuchElementException ex) {
            LOGGER.debug("No MEC host records exist created by Admin");
        }
        List<MecHost> mecHosts;
        try {
            mecHosts = getTenantRecords(tenantId, repository);
            for (MecHost host : mecHosts) {
                MecHostDto mecHostDto = InventoryUtilities.getModelMapper().map(host, MecHostDto.class);
                mecHostDtos.add(mecHostDto);
            }
        } catch (NoSuchElementException ex) {
            if (mecHostsAdmin.isEmpty()) {
                throw ex;
            }
        }
        if (checkEmpty(mecHostsAdmin, mecHostDtos)) {
            return mecHostDtos;
        }
        return addHostDtos(mecHostsAdmin, mecHostDtos);
    }
}
